using System;
using System.Collections.Generic;
using System.Diagnostics;
using BizTalk.BuildGenerator.MetaData;
using BizTalk.BuildGenerator.MetaData.ResourceAdapters;
using Microsoft.BizTalk.ApplicationDeployment;
using System.Reflection;

namespace BizTalk.BuildGenerator
{
    /// <summary>
    /// Helper class for getting applications
    /// </summary>
    public sealed class BizTalkHelper
    {
        private const string BizTalkManagementDatabaseName = "BizTalkMgmtDb";
        private const string BizTalkDatabaseServerName = ".";

        /// <summary>
        /// Gets all of the applications deployed on the server
        /// </summary>        
        /// <returns></returns>
        public static List<string> GetApplications()
        {
            List<string> applicationNames = new List<string>();
            using (Group group = new Group())
            {
                group.DBName = BizTalkManagementDatabaseName;
                group.DBServer = BizTalkDatabaseServerName;
                foreach (Application app in group.Applications)
                {
                    applicationNames.Add(app.Name);
                }
            }
            return applicationNames;
        }       
        /// <summary>
        /// Describes the application
        /// </summary>
        /// <param name="applicationName"></param>
        /// <returns></returns>
        public static ApplicationDescription DescribeApplication(string applicationName)
        {
            ApplicationDescription applicationDescription = new ApplicationDescription();

            using (Group group = new Group())
            {
                group.DBName = BizTalkManagementDatabaseName;
                group.DBServer = BizTalkDatabaseServerName;
                Application app = group.Applications[applicationName];

                applicationDescription.Name = app.Name;
                applicationDescription.Description = app.Description;

                foreach (Resource resource in app.ResourceCollection)
                {
                    Debug.WriteLine("Resource Type: " + resource.ResourceType);
                    ApplicationResource applicationResource = new ApplicationResource();
                    applicationResource.FullName = resource.Luid;
                    applicationResource.Type = GetResourceType(resource.ResourceType);
                 
                    if (IncludeResource(applicationResource))
                    {
                        AddResourceReferences(resource, applicationResource);
                        AddResourceProperties(resource, applicationResource);
                        applicationDescription.Resources.Add(applicationResource);
                    }
                }

                AddDependantResources(applicationDescription);

                SortResources(applicationDescription);
            }
            return applicationDescription;
        }

        #region Private Methods
        /// <summary>
        /// Returns just the stuff after the : in the type name
        /// </summary>
        /// <param name="typeDescription"></param>
        /// <returns></returns>
        private static string GetResourceType(string typeDescription)
        {
            int splitPosition = typeDescription.IndexOf(":");
            return typeDescription.Substring(splitPosition + 1);
        }
        /// <summary>
        /// Sorts the resources based on their dependancies.  They need to be sorted so when we generate a script to deploy they will
        /// be loaded to BizTalk in the correct order
        /// </summary>
        /// <param name="applicationDescription"></param>
        private static void SortResources(ApplicationDescription applicationDescription)
        {
            applicationDescription.Resources.Sort(new ApplicationResourceComparer());
            Trace.WriteLine("The resource order after Sort is:");
            foreach (ApplicationResource r in applicationDescription.Resources)
                Trace.WriteLine(r.FullName);
        }
        /// <summary>
        /// Loads up the resource properties
        /// </summary>
        /// <param name="resource"></param>
        /// <param name="applicationResource"></param>
        private static void AddResourceProperties(Resource resource, ApplicationResource applicationResource)
        {
            IEnumerator<KeyValuePair<string, object>> enumerator = resource.Properties.GetEnumerator();
            while (enumerator.MoveNext())
            {
                Debug.WriteLine(enumerator.Current.Key + ":" + Convert.ToString(enumerator.Current.Value));
                ResourceProperty property = new ResourceProperty();
                property.Name = enumerator.Current.Key;
                property.Value = enumerator.Current.Value;
                applicationResource.Properties.Add(property);
            }
        }
        /// <summary>
        /// Uses reflection to load the references of each application resource
        /// </summary>
        /// <param name="applicationResource"></param>
        /// <param name="resource"></param>
        private static void AddResourceReferences(Resource resource, ApplicationResource applicationResource)
        {
            const string SourceLocationPropertyName = "SourceLocation";

            if (applicationResource.Type == ResourceTypes.Assembly || applicationResource.Type == ResourceTypes.BizTalkAssembly)
            {
                string location = (string)resource.Properties[SourceLocationPropertyName];
                Assembly a = Assembly.LoadFile(location);
                foreach (AssemblyName name in a.GetReferencedAssemblies())
                {
                    Trace.WriteLine(string.Format("Reflected Assembly: {0} depends on {1}", a.FullName, name.FullName));
                    applicationResource.References.Add(name.FullName);
                }
            }                        

        }
        /// <summary>
        /// Looks at the references for each resource and creates links between resources based on the references.  This then allows the resources to be later sorted based on these dependancies
        /// </summary>
        /// <param name="applicationDescription"></param>
        private static void AddDependantResources(ApplicationDescription applicationDescription)
        {            
            foreach (ApplicationResource resource in applicationDescription.Resources)
            {
                foreach (ApplicationResource dependantResource in applicationDescription.Resources)
                {
                    if (dependantResource != resource)
                    {
                        if (resource.References.Contains(dependantResource.FullName))
                            resource.DependantResources.Add(dependantResource);
                    }
                }
            }
        }
        /// <summary>
        /// Validates the resource to ensure it should be included
        /// </summary>
        /// <param name="resource"></param>
        /// <returns></returns>
        private static bool IncludeResource(ApplicationResource resource)
        {
            bool include = true;

            if (resource.Type == ResourceTypes.BizTalkBinding)
            {
                BindingResourceAdapter bindingAdapter = BindingResourceAdapter.Create(resource);
                if (string.IsNullOrEmpty(bindingAdapter.SourceLocation))
                    include = false;
            }

            return include;
        }
        #endregion
    }
}